package figure8;

import java.io.File;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

import javax.sound.sampled.AudioSystem;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;

import figure8.Network.Client;
import figure8.Network.Message;
import figure8.Network.NetworkLayer;
import figure8.Network.Server;
import figure8.Network.Message.Type;
import figure8.Project.InvalidProjectException;
import figure8.GUI.AboutWindow;
import figure8.GUI.AudioInputWindow;
import figure8.GUI.ContactWindow;
import figure8.GUI.ControlPanel;
import figure8.GUI.CustomiseWindow;
import figure8.GUI.MainWindow;
import figure8.GUI.NewProjectWindow;
import figure8.GUI.NotSavedWindow;
import figure8.GUI.Waveform;
import figure8.events.NetworkDisconnectEvent;
import figure8.events.NetworkEventListener;
import figure8.events.NetworkMessageEvent;
import figure8.events.NetworkNewClientEvent;
import figure8.events.PlaybackEvent;
import figure8.events.PlaybackListener;
import figure8.events.TrackAddedEvent;
import figure8.events.TrackChangeListener;
import figure8.events.TrackChangedEvent;
import figure8.events.TrackEventListener;
import figure8.events.TrackRemovedEvent;

public class Controller implements NetworkEventListener, TrackEventListener, TrackChangeListener {
	private Project project = null;
	private MainWindow window;
	private Rec rec;
	protected List<PlaybackListener> listeners;
	private boolean loaded;
	private CustomiseWindow customiseWin;

	private List<Waveform> waveforms;
	
	private NetworkLayer network;
	
	public static void main(String[] args) {
		System.out.println("Welcome to Figure8. This software is open source.");
		System.out.println("Please feel free to edit the source code if you wish");
		System.out.println("If you feel you have made any significant changes,");
		System.out.println("please contact us and we will add these to a stable release.");
		Controller controller = new Controller();
	}

	public Controller() {
		this.listeners = new ArrayList<PlaybackListener>();
		this.window = new MainWindow(this);
		this.waveforms = new LinkedList<Waveform>();
	}
	
	/*public Controller(File dir) {
		super();
		
		Project project = null;
		try {
			project = new Project(dir, this);
		} catch (InvalidProjectException e) {
			e.printStackTrace();
		}
		this.loaded = true;
		this.setProject(project);
		
	}*/

	public void playback() {
		this.rec.stopPress();
	}

	public void record() {
		this.window.getProjectPanel().startBar();
		this.rec.recPress(window, false);
		
	}

	public void keep() {
		Track track = this.rec.confirm();
		if (track != null) {
			project.addTrack(track);
			window.getFrame().pack();
		}
	}

	public void stop() {
		this.rec.stopPress();
		this.project.stop();
		this.playbackState(PlaybackEvent.State.STOPPED);
	}

	public void switchInput() {
		stop();
		this.rec.recPress(window, true);
		this.window.getProjectPanel().startBar();
		this.playbackState(PlaybackEvent.State.PLAYING);
		window.getControlPanel().setState(ControlPanel.ControlPanelStates.RECORDING);
	}
	
	public void deleteTrack(Track track) {
		this.project.deleteTrack(track, null);
		this.rec.trackDeleted(track);
		window.getFrame().pack();
	}

	public void updateTiming(LoopTiming timing) {
		this.rec.stopPress();
		this.playbackState(PlaybackEvent.State.STOPPED);
		this.project.updateTimings(timing);
		this.window.updateTiming(this.project);
		window.getFrame().pack();
	}

	public void new_() {
		if (project != null) {
			NotSavedWindow warningWindow = new NotSavedWindow(this, 5);
		}
		else
		{
			loaded = false;
			NewProjectWindow newProject = new NewProjectWindow(this, window, null);
		}
	}
	

	
	public void newWin()
	{
		DrumMachineTrack drum = null;
		//this.closeProject();
		if (rec != null) drum =  this.project.getDrums();
		loaded = false;
		NewProjectWindow newProject = new NewProjectWindow(this, window, drum);
	}

	public void open() {
		//Create popup to make sure unsaved project is saved first, then creates Open dialog
		if (project != null) {
			NotSavedWindow warningWindow = new NotSavedWindow(this, 4);
			this.playbackState(PlaybackEvent.State.STOPPED);
		}
		else
		{
			this.openWin();
		}
	}
	
	public void newOpenController() {
		DrumMachineTrack drum = this.project.getDrums();
		this.closeProject();
		//Controller controller2 = new Controller();
		//controller2.openWin(1);
		openWin(drum);
	}
	
	public void newController() {
		DrumMachineTrack drum = this.project.getDrums();
		Controller controller2 = new Controller();
		controller2.newWin();
		this.closeProject();
		this.window.removeTrayIcon();
		this.window.getFrame().dispose();
	}
	
	public boolean isProject (String dir) {
	 	File directory = new File(dir);
	 	int check = 0;
	 	File[] files = directory.listFiles();
	 	if (files != null) {
		 	for (int i = 0; i < files.length; i++) {
		 		String name = files[i].toString();
		 		if(name.contains("drum_machine.f8p")) check++;
		 		else if(name.contains("list_tracks.f8p")) check++;
		 		else if(name.contains("timing.f8p")) check++;
		 		else if(name.contains("Recordings")) check++;
		 	}
	 	}
	 	if (check == 4) return true;
	 	else return false;
	}
	
	public void openWin()
	{
		loaded = true;
		JFileChooser openFC = new JFileChooser();
		openFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		int returnVal = openFC.showDialog(window.getFrame(), "Open");
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			String dir = openFC.getSelectedFile().getAbsolutePath();
			//if (this.project != null) {
				//this.closeProject();
				//Controller controllerNew = new Controller ();
			//}
			//else {
			if (isProject(dir)) {
				Project project = null;
				try {
					project = new Project(new File(dir), this);
				} catch (InvalidProjectException e) {
					System.err.println("Could not load project.");
				}
				if (project != null) {
					this.setProject(project);
				}
				//}
			}
			else JOptionPane.showMessageDialog(window.getFrame(), "Error: Chosen directory missing neccesary files.");

		} else
			System.out.println("Open window cancel button clicked");
		
	}
	
	public void openWin(DrumMachineTrack drum)
	{
		loaded = true;
		JFileChooser openFC = new JFileChooser();
		openFC.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
		int returnVal = openFC.showDialog(window.getFrame(), "Open");
		if (returnVal == JFileChooser.APPROVE_OPTION) {
			String dir = openFC.getSelectedFile().getAbsolutePath();
			if (isProject(dir)) {
				if (this.project != null) {
					//this.closeProject();
					Controller controllerNew = new Controller();
				}
				else {
					Project project = null;
					try {
						project = new Project(new File(dir), this);
					} catch (InvalidProjectException e) {
						System.err.println("Could not load project.");
					}
					if (project != null) {
						this.setProject(project, drum);
					}
				}
			}
			else {
				JOptionPane.showMessageDialog(window.getFrame(), "Error: Chosen directory missing neccesary files. Please try again.");
				openWin(drum);
			}

		} else
			System.out.println("Open window cancel button clicked");
		
	}

	public void save() {
		this.project.save();
		this.rec.save();
	}
	
	public void openSequencer() {
		if (project != null) {
			NotSavedWindow warningWindow = new NotSavedWindow(this, 6);
		}
		else
		{
			this.window.getFrame().dispose();
			Seq seq = new Seq(this);
		}
	}
	
	public void openSequencer2() {
		DrumMachineTrack drum;
		if (this.rec != null) {
			drum = this.project.getDrums();
		} else drum = null;
		closeProject();
		Seq seq = new Seq(this, drum);
		this.window.removeTrayIcon();
		this.window.getFrame().dispose();
	}
	
	

	public void export() {
		System.err.println("Exporting not yet implemented!");
	}

	public void exit() {
		//Create popup to make sure unsaved project is saved first, then exits the program
		if (project != null) {
			NotSavedWindow warningWindow = new NotSavedWindow(this, 2);
		}
		else
		{
			System.exit(0);
		}
	}
	
	public void closeProject() {
		this.stop();
		this.rec.closeLines();
		this.project.closeLines();
		this.project = null;
		this.rec = null;
	}
	
	public void about() {
		AboutWindow about = new AboutWindow(window);
	}

	public void contactUs() {
		ContactWindow contactUs = new ContactWindow(window);
	}

	public void userGuide() {
		try {
			File guide = new File("src/figure8/GUI/UserGuide.pdf");
			String path = guide.getAbsolutePath();
			if(guide.exists()) {
				Process p = null;
				if(Controller.isWindows()) {
					p = Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + path);
				}
				else if(Controller.isMac()) {
					p = Runtime.getRuntime().exec("open " + path);
				}
				else if(Controller.isUnix()) {
					String[] cmd = {"gnome-open", guide.getAbsolutePath()};
					p = Runtime.getRuntime().exec(cmd);
				}
				p.waitFor();
			}
			else {
				System.err.println("User guide file does not exist");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public static boolean isWindows() {
		 
		String OS = System.getProperty("os.name").toLowerCase();
	    return (OS.indexOf( "win" ) >= 0); 
	}
 
	public static boolean isMac() {
 
		String OS = System.getProperty("os.name").toLowerCase();
	    return (OS.indexOf( "mac" ) >= 0); 
	}
 
	public static boolean isUnix() {
 
		String OS = System.getProperty("os.name").toLowerCase();
	    return (OS.indexOf( "nix") >=0 || OS.indexOf( "nux") >=0);
	}

	public Project getProject() {
		return project;
	}	
	
	public MainWindow getWindow() {
		return window;
	}
	
	public void setProject(Project project) {
		this.project = project;
		window.getProjectPanel().setProject(project);
		window.getControlPanel().setState(ControlPanel.ControlPanelStates.STOPPED);
		window.getFrame().pack();
		window.getMenuBar().enableSaving();
		
		project.addTrackEventListener(this);
		
		//if new project then first rec, if loaded from file then the else rec
		if(this.loaded == false) {
			this.rec = new Rec(this, project.getName(), project.getTiming(), project.getLocation(), null);
		}
		else {
			this.rec = new Rec(this, project.getTiming(), project.getLocation(), project.getName(), project, null);
			window.getFrame().pack();
		}
	}
	
	public void setProject(Project project, DrumMachineTrack drum) {
		this.project = project;
		window.getProjectPanel().setProject(project);
		window.getControlPanel().setState(ControlPanel.ControlPanelStates.STOPPED);
		window.getFrame().pack();
		window.getMenuBar().enableSaving();
		
		project.addTrackEventListener(this);
		
		//if new project then first rec, if loaded from file then the else rec
		if(this.loaded == false) {
			this.rec = new Rec(this, project.getName(), project.getTiming(), project.getLocation(), drum);
		}
		else {
			this.rec = new Rec(this, project.getTiming(), project.getLocation(), project.getName(), project, drum);
			window.getFrame().pack();
		}
	}
	
	public void playbackState(PlaybackEvent.State state) {
		this.firePlaybackEvent(state);
	}
	
	// Events listener stuff
	public void addPlaybackListener(PlaybackListener listener) {
		this.listeners.add(listener);
	}

	public void removePlaybackListener(PlaybackListener listener) {
		this.listeners.remove(listener);
	}

	protected void firePlaybackEvent(PlaybackEvent.State state) {
		for (PlaybackListener listener : this.listeners) {
			listener.playbackChanged(new PlaybackEvent(this, state));
		}
	}
	
	
	public void addWaveform(Waveform waveform) {
		this.waveforms.add(waveform);
	}
	
	public void removeWaveform(Waveform waveform) {
		this.waveforms.remove(waveform);
	}
	
	public void connect(String address, int port) {
		try {
			this.network = new NetworkLayer(port);
			this.network.connect(address);
			
			(new Thread(this.network)).start();
			
			window.getMenuBar().setConnected();
			
			this.network.addNetworkEventListener(this);
			
		} catch (IOException e) {
			System.err.println("Can't connect.");
		}
	}
	
	public void disconnect() {
		this.network.disconnect("Manual disconnect.");
		
		window.getMenuBar().setDisconnected();
	} 
	
	public void host(int port) {
		this.network = new NetworkLayer(port);
		this.network.host();
		
		(new Thread(this.network)).start();
		
		window.getMenuBar().setConnected();
		
		this.network.addNetworkEventListener(this);
	}

	public void chat(String text) {
		Message message = new Message(Type.CLIENT_CHAT, text);
		
		this.network.send(message);
	}
	
	@Override
	public void networkDisconnect(NetworkDisconnectEvent event) {
		System.out.println("We were disconnected :(");
	}

	@Override
	public void networkMessage(NetworkMessageEvent event) {
		Message message = event.getMessage();
		
		int id;
		Track track;
		String parts[];
		
		switch(message.type) {
		case CLIENT_JOINED:
			String data[] = message.data.split(" ");
			String name = data[1];
			
			id = Integer.parseInt(data[0]);
			
			System.out.println(name + " (" + id + ") connected.");
			break;
		case CLIENT_LEFT:
			id = Integer.parseInt(message.data);
			
			System.out.println(" (" + id + ") disconnected.");
			break;
		case CLIENT_CHAT:
			System.out.println("Someone said " + message.data);
			//new __HORRIBLE_CHAT_DEMO(this, message.data);
			break;
		case SERVER_SHUTDOWN:
			System.out.println("Server shut down (" + message.data + ").");
			window.getMenuBar().setDisconnected();
			break;
		case PROJECT_NEW:
			this.project.fromString(message.data, event.getClient());
			this.rec.setTiming(this.project.getTiming());
			this.window.getProjectPanel().setProject(this.project);
			break;
		case TRACK_ADD:
			DownloadingTrack track2 = DownloadingTrack.deserialize(this.project, message.data, event.getClient());
			
			System.out.println("Received: " + track2.getID());
			this.project.addTrack(track2, event.getClient());
			
			track2.finalize();
			
			break;
		case TRACK_REMOVE:
			id = Integer.parseInt(message.data);
			track = this.project.getTrack(id);
			this.project.deleteTrack(track, event.getClient());
			break;
			
		case TRACK_UPDATE:
			parts = message.data.split(" ");
			
			System.err.println(message.data);
			
			id = Integer.parseInt(parts[1]);
			
			track = this.project.getTrack(id);
			
			if (track != null) {
				track.updateMeta(message.data.split(" ", 2)[1]);
			} else {
				System.err.println("Missing track " + id + " for some reason");
				
				for (Track track3 : this.project.trackIDs.values()) {
					System.out.println(track3.getID());
				}
			}
			break;
		case TRACK_PIECE:
			parts = message.data.split(" ");
			id = Integer.parseInt(parts[0]);
			DownloadingTrack track3 = (DownloadingTrack) this.project.getTrack(id);
			
			int start = message.data.indexOf(' ');
			
			track3.filePart(message.data.substring(start+1));
			
			break;
		}
	}

	@Override
	public void networkNewClient(NetworkNewClientEvent event) {
		Client client = event.getClient();
		
		project.send(client);
	}

	@Override
	public void trackAdded(TrackAddedEvent event) {
		event.getTrack().addChangeListener(this);
		
		if (this.network != null) {
			if (event.getClient() == null || this.network.isHost()) {
				event.getTrack().send(this.network, event.getClient());
			}
		}
	}

	@Override
	public void trackRemoved(TrackRemovedEvent event) {
		event.getTrack().removeChangeListener(this);
		
		if (this.network != null) {
			if (event.getClient() == null || this.network.isHost()) {
				this.network.send(new Message(Type.TRACK_REMOVE, "" + event.getTrack().getID()), event.getClient());
			}
		}
	}
	
	@Override
	public void trackChanged(TrackChangedEvent event) {
		if (this.network != null && (event.isLocal() || this.network.isHost())) {
			System.out.println("Forwarding change, " + event.isLocal() + " " + this.network.isHost());
			
			event.getTrack().sendMeta(this.network);
		}
	}

	public void soloTrack(Track track) {
		this.project.soloTrack(track);
		
	}
}